/*
 * Projekt: GMU 2013 - Canny hranovy detektor OpenCL/CUDA
 * Autori: Pavel Nevrala - xnevra00 (xnevra00@stud.fit.vutbr.cz)
 *         Martin Jurak - xjurak02 (xjurak02@stud.fit.vutbr.cz)
 */
#include "CannyDetectorCV.h"


CannyDetectorCV::CannyDetectorCV(void)//definice konstruktoru
{
}


CannyDetectorCV::~CannyDetectorCV(void)//definice destruktoru
{
}

void CannyDetectorCV::detectEdges(ImageCL &inImage, ImageCL &outImage, GaussianMask &gaussMask, int threshold1, int threshold2){

	Mat src, GX, GY, mag, angles, localMax, thrImage;
	double gaussT,sobelT,nonMaxSupT,doubleThrT, cannyT;
	__int64 start, end;

	inImage.getImageCV()->convertTo(src,TYPE);
	Mat dst = Mat(inImage.width(), inImage.height(), TYPE);

	// Gaussuv filtr
	start = getTickCount();
	sepFilter2D(src, src, TYPE, *gaussMask.getGaussianMaskCV(), *gaussMask.getGaussianMaskCV());	
	end = getTickCount();
	gaussT = (end - start)/ getTickFrequency();
	
	// Sobeluv operator
	angles = Mat(src.rows,src.cols,TYPE);
	mag = Mat(src.rows,src.cols,TYPE);
	start = getTickCount();	
	// Sobel filtr X
	Sobel(src, GX, TYPE, 1, 0, 3);
	// Sobel filtr Y
	Sobel(src, GY, TYPE, 0, 1, 3);	
	magnitude(GX,GY,mag);	
	roundedPhase(GX, GY, angles);
	end = getTickCount();
	sobelT = (end - start)/ getTickFrequency();
	
	// Hledani lokalnich maxim
	localMax = Mat(GX.rows,GX.cols,TYPE);
	start = getTickCount();	
	nonMaxSup(mag, angles, localMax);
	end = getTickCount();
	nonMaxSupT = (end - start)/ getTickFrequency();	
	
	// Double thresholding s hysterezi
	thrImage = Mat(GX.rows,GX.cols,TYPE);
	start = getTickCount();	
	doubleThresholding(localMax, angles, thrImage, threshold1, threshold2);
	end = getTickCount();
	doubleThrT = (end - start)/ getTickFrequency();

	// Canny detector v OpenCV
	src.convertTo(src,CV_8U);
	start = getTickCount();	
	Canny( src, src, threshold1, threshold2, 3 );
	end = getTickCount();
	cannyT = (end - start)/ getTickFrequency();

	thrImage.copyTo(dst);

	// Vypis jednotlivych casu fazi Cannyho hranoveho detektoru vytvoreneho v OpenCV
	std::cout << "\n*****OPENCV CANNY DETECTOR*****" << std::endl;
	std::cout << "OpenCV Gauss filter:         " << gaussT << "s"<< std::endl;
	std::cout << "OpenCV Sobel:                " << sobelT << "s"<< std::endl;
	std::cout << "OpenCV nonMaxSup:            " << nonMaxSupT << "s"<< std::endl;
	std::cout << "OpenCV Doublethrsholding:    " << doubleThrT << "s"<< std::endl;
	std::cout << "OpenCV vlastni implementace: " << (gaussT+sobelT+nonMaxSupT+doubleThrT) << "s"<< std::endl;
	// cas vypoctu Cannyho detektoru integrovaneho v OpenCV
	std::cout << "Canny detector OpenCV :      " << cannyT << "s"<< std::endl;
	
	outImage.loadImage(dst);

}

/*
 * Vypocet smeru hran v obraze
 * @GX - Obraz s gradientem ve smeru X
 * @GY - Obraz s gradientem ve smeru Y
 * @angles - vystupni obraz se smery jednotlivych hran	
 */
void CannyDetectorCV::roundedPhase(Mat GX, Mat GY, Mat angles){	

	float direction;
	for(int x=0; x < GX.cols; x++){
		for(int y=0; y < GX.rows; y++){

			// vypocet uhlu
			direction = fastAtan2(GY.at<float>(y,x), GX.at<float>(y,x));
			
			if(direction < 0){
				direction = 360 + direction;
			}

			//zaoktrouhleni na uhel 0
			if(((direction <= 22.5f) && (direction >= 0.0f)) || ((direction <= 360.0f) && (direction >= 337.5f)) || ((direction <= 202.5f) && (direction >= 157.5f))){
				direction = 0.0f;
			}
			//zaoktrouhleni na uhel 45
			else if (((direction <= 67.5f) && (direction > 22.5f)) || ((direction <= 247.5f) && (direction > 202.5f))){
				direction = 45.0f;
			}
			//zaoktrouhleni na uhel 90
			else if (((direction <= 112.5f) && (direction > 67.5f)) || ((direction <= 292.5f) && (direction > 247.5f))){
				direction = 90.0f;
			}
			//zaoktrouhleni na uhel 135
			else if (((direction < 157.5f) && (direction > 112.5f)) || ((direction < 337.5f) && (direction > 292.5f))){
				direction = 135.0f;		
			}

			angles.at<float>(y,x) = direction;
		}
	}
}

/*
 * Ztenceni hran v obraze
 * Podle smeru daneho pixelu jsou vybrany dva pixeli z osmikololi (napr. pro uhel 0 predchozi a nasledujici pixel obrazu)
 * a porovna jejich intenzitu. Pokud ma jeden z okolnich vyssi intenzitu je aktualni pixel potlacen nastaven na 0 jinak 
 * zustane zachovan.
 * @src - gradient obrazu
 * @angle - obraz se smery jednotlivych hran	
 * @supImage - vystupni obraz se ztencenymi hranami
 */
void CannyDetectorCV::nonMaxSup(Mat src, Mat angles, Mat supImage){

	float first, second, direction;
	float center;
	int width = src.cols, height = src.rows;

	for(int x=0; x < width; x++){
		for(int y=0; y < height; y++){

			center = src.at<float>(y,x);
			direction = angles.at<float>(y,x);

			if(direction == 0 ){				
				if (x == 0){
					first = 0.0f;
					second = src.at<float>(Point(x+1,y));
				}else if (x == width-1){
					first = src.at<float>(Point(x-1,y));
					second = 0.0f;
				}else if (x > 0 && x < width-1){
					first = src.at<float>(Point(x-1,y));
					second = src.at<float>(Point(x+1,y));
				}		
			}
			else if(direction == 90){
				if (y == 0){
					first = 0.0f;
					second = src.at<float>(Point(x,y+1));
				}else if (y == height-1){
					first = src.at<float>(Point(x,y-1));
					second = 0.0f;
				}else if ((y > 0) && (y < height-1)){
					first = src.at<float>(Point(x,y-1));
					second = src.at<float>(Point(x,y+1));
				}		
			}
			else if(direction == 45){
				if (((x == 0) && (y == height-1)) || ((x == width-1) && (y == 0))){
					first = 0.0f;
					second = 0.0f;
				}else if (((x < width) && (x > 0) && (y == height-1)) || ((x == width-1 ) && (y < height) && (y > 0))){
					first = src.at<float>(Point(x-1,y-1));
					second = 0.0f;
				}else if (((x < width-1) && (y == 0)) || ((x == 0) && (y < height))){
					first = 0.0f;
					second = src.at<float>(Point(x+1,y+1));
				}else if ((x > 0) && (x < width-1) && (y > 0) && (y < height-1)){
					first = src.at<float>(Point(x-1,y-1));
					second = src.at<float>(Point(x+1,y+1));
				}
			}else if(direction == 135){
				if (((x == 0) && (y == 0)) || ((x == width-1) && (y == height-1))){
					first = 0.0f;
					second = 0.0f;
				}else if (((x == width-1) && (y < height-1) && (y > 0)) || ((x > 0) && (x < width) && (y == 0))){
					first = src.at<float>(Point(x-1,y+1));
					second = 0.0f;
				}else if (((x == 0) && (y < height) && (y > 0)) || ((x >= 0 ) && (x < width-1) && (y==height-1) ) ){
					first = 0.0f;
					second = src.at<float>(Point(x+1,y-1));
				}else if ((x > 0) && (x < width-1) && (y > 0) && (y < height-1)){
					first = src.at<float>(Point(x+1,y-1));
					second = src.at<float>(Point(x-1,y+1));
				}
			}
				
			if((center < first) || (center < second) || (((x== width-1) && (x == 0)) && ((y== height-1) && (y == 0))) ){
				supImage.at<float>(y,x) = 0.0f;
			}			
			else{
				supImage.at<float>(y,x) = center;
			}		
		}
	}
}

/*
 * Eliminace nevyznamnych hran
 * Prochazi pixel po pixelu. Pokud se jeden o pixel na silne hrane, zjisti smer gradientu v tomto bode
 * a zkouma pixel v tomto smeru. Pokud se jedna o slabou hranu (mezi dolnim a hornim prahem) oznaciji ji jako silnou.
 * @src - gradient obrazu
 * @angle - braz se smery jednotlivych hran	
 * @thrImage - vystupni obraz se bez nevyznamnych hran
 * @thrLow - dolni prah
 * @thrHigh - horni prah
 */
void CannyDetectorCV::doubleThresholding(Mat src, Mat angles, Mat thrImage, int thrLow, int thrHigh){
	float direction;
	int new_x,new_y;
	int width = src.cols, height = src.rows;
	for(int x=0; x < width; x++){
		for(int y=0; y < height; y++){

			direction = angles.at<float>(y,x);

			new_x = x;
			new_y = y;
			if(src.at<float>(y,x) >= (float)thrHigh){

				// pixel lezi na silne hrane
				// podle smaru gradientu prochazi okolni pixely
				if(direction == 0 ){
					while((new_x >= 0) && (new_x < width) && (angles.at<float>(new_y, new_x) == 0) && (src.at<float>(new_y, new_x)>(float)thrLow)){
						thrImage.at<float>(new_y, new_x)=255.0f;
						new_x = new_x+1;
						new_y = new_y;					                   
					}
				}
				else if(direction == 90){
					while((new_y >= 0) && (new_y < height) && (angles.at<float>(new_y, new_x) == 90) && (src.at<float>(new_y, new_x)>(float)thrLow)){
						thrImage.at<float>(new_y, new_x)=255.0f;
						new_x = new_x;
						new_y = new_y+1;					                  
					}				
				}
				else if(direction == 45){
					while((new_y >= 0) && (new_y < height) && (new_x >= 0) && (new_x < width) && (angles.at<float>(new_y, new_x) == 45) && (src.at<float>(new_y, new_x)>(float)thrLow)){
						thrImage.at<float>(new_y, new_x)=255.0f;
						new_x = new_x+1;
						new_y = new_y-1;					               
					}
				}else if(direction == 135){
					while((new_y >= 0) && (new_y < height) && (new_x >= 0) && (new_x < width) && (angles.at<float>(new_y, new_x) == 135) && (src.at<float>(new_y, new_x)>(float)thrLow)){
						thrImage.at<float>(new_y, new_x)=255.0f;
						new_x = new_x+1;
						new_y = new_y+1;                  
					}
				}			
			}
		}
	}
}